package cn.sc.summer.mybatis.aspect;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.sc.summer.mybatis.annotation.dynamicdatasource.DataSource;
import cn.sc.summer.mybatis.annotation.dynamicdatasource.MapperSource;
import cn.sc.summer.mybatis.context.DynamicDataSourceContext;
import cn.sc.summer.mybatis.enums.DataSourceEnum;
import cn.sc.summer.constant.mybatisplus.datasource.DataSourceConstant;
import cn.sc.summer.exception.enums.CommonExceptionEnum;
import cn.sc.summer.mybatis.config.MyBatisPlusConfig;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.aop.support.AopUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * 类名：动态数据源切面
 *
 * @author a-xin
 * @date 2024/2/1 11:25
 */
@Slf4j
@Aspect
@Component
public class DataSourceAspect {

    public static final ThreadLocal<Boolean> RESET_DB = new ThreadLocal<>();

    public static final ThreadLocal<String> CURRENT_DB = new ThreadLocal<>();

    @Pointcut("@annotation(cn.sc.summer.mybatis.annotation.dynamicdatasource.DataSource)")
    public void serviceDataSourcePointCut() {
    }

    @Pointcut("execution(* cn.sc.summer.mybatis.base.SuperMapper+.*(..))")
    public void mapperDataSourcePointCut() {
    }

    @Around("serviceDataSourcePointCut()")
    public Object serviceDataSourceAround(ProceedingJoinPoint point) throws Throwable {
        return serviceDataSourceProceed(point);
    }

    @Around("mapperDataSourcePointCut()")
    public Object mapperSourceAround(ProceedingJoinPoint point) throws Throwable {
        return mapperDataSourceProceed(point);
    }

    /**
     * mapper层动态数据源处理
     *
     * @param point 切点
     * @return 返回数据
     * @throws Throwable 异常信息
     */
    private Object mapperDataSourceProceed(ProceedingJoinPoint point) throws Throwable {
        if (CharSequenceUtil.isBlank(DynamicDataSourceContext.getDataSource())) {
            DynamicDataSourceContext.setDataSource(DataSourceEnum.MASTER.getType());
            CURRENT_DB.set(DataSourceEnum.MASTER.getType());
        }

        String proxyMapperName = AopUtils.getTargetClass(point.getTarget()).getGenericInterfaces()[0].getTypeName();
        Map<String, String> mapperDataSourceMap = SpringUtil.getBean(MyBatisPlusConfig.MAPPER_SOURCE_BEAN_NAME);
        if (mapperDataSourceMap.containsKey(proxyMapperName)) {
            DynamicDataSourceContext.setDataSource(mapperDataSourceMap.get(proxyMapperName));
            log.debug("==> [{}] Start set datasource is: {} ", proxyMapperName, mapperDataSourceMap.get(proxyMapperName));
        }

        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        MapperSource mapperSource = method.getAnnotation(MapperSource.class);

        if (ObjectUtil.isNotNull(mapperSource)) {
            String target = mapperSource.target();
            if (CharSequenceUtil.isNotBlank(target)) {
                CommonExceptionEnum.NORMAL_EXCEPTION.exceptionIf(!DataSourceConstant.DATA_SOURCE_MAP.containsKey(target), "No corresponding data source: {} information exists", target);
                DynamicDataSourceContext.setDataSource(target);
                log.debug("==> [{}] [{}] Start set datasource is: {} ", proxyMapperName, method.getName(), target);
            } else {
                DynamicDataSourceContext.setDataSource(mapperSource.value().getType());
                log.debug("==> [{}] [{}] Start set datasource is: {} ", proxyMapperName, method.getName(), mapperSource.value().getType());
            }
        }

        try {
            return point.proceed();
        } finally {
            if (ObjectUtil.isNotNull(RESET_DB.get()) && RESET_DB.get()) {
                DynamicDataSourceContext.setDataSource(CURRENT_DB.get());
                log.debug("==> Start reset the datasource information...");
            } else {
                DynamicDataSourceContext.clearDataSource();
                log.debug("==> Start clean the datasource information...");
            }
        }

    }

    /**
     * service层动态数据源处理
     *
     * @param point 切点
     * @return 返回数据
     * @throws Throwable 异常信息
     */
    private Object serviceDataSourceProceed(ProceedingJoinPoint point) throws Throwable {

        if (CharSequenceUtil.isBlank(DynamicDataSourceContext.getDataSource())) {
            DynamicDataSourceContext.setDataSource(DataSourceEnum.MASTER.getType());
        }

        Class<?> pointClass = point.getTarget().getClass();

        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        DataSource dataSource = method.getAnnotation(DataSource.class);

        if (ObjectUtil.isNotNull(dataSource)) {
            String target = dataSource.target();
            if (CharSequenceUtil.isNotBlank(target)) {
                CommonExceptionEnum.NORMAL_EXCEPTION.exceptionIf(!DataSourceConstant.DATA_SOURCE_MAP.containsKey(target), "No corresponding data source: {} information exists", target);
                DynamicDataSourceContext.setDataSource(target);
                log.debug("==> [{}] [{}] Start set datasource is: {} ", pointClass.getName(), method.getName(), target);
            } else {
                DynamicDataSourceContext.setDataSource(dataSource.value().getType());
                log.debug("==> [{}] [{}] Start set datasource is: {} ", pointClass.getName(), method.getName(), dataSource.value().getType());
            }
            RESET_DB.set(Boolean.TRUE);
        }

        try {
            return point.proceed();
        } finally {
            DynamicDataSourceContext.clearDataSource();
            log.debug("==> Start clean the datasource information...");
        }
    }

}
